const Koa = require('koa');
const hbs = require('koa-hbs');
const json = require('koa-json');
const cors = require('koa2-cors');
const koabody = require('koa-body');
const Router = require('koa-router');
const static = require('koa-static');

const path = require('path');
const consul = require('consul');
const crypto = require('crypto');

require('./exts/KoaRouterExt');

const { getDbHelper, getSQLite } = require('./DbHelpers');
const { env, httpClient, getResponse } = require('./index');

const hbsHeper = require('./exts/RegisterHbsHelper');

// 解决返回json日期序列化问题
Date.prototype.toJSON = function () {
  this.setHours(this.getHours() + (this.getTimezoneOffset() / 60));

  return this.format();
};

process.env.UV_THREADPOOL_SIZE = '512';

let server = null;

const app = new Koa();
const interceptors = [];
const staticPaths = [];

function useStatic (path) {
  if (!path) {
    throw '未设置路径！';
  }

  staticPaths.push(path);
}

function useInterceptor (callback) {
  if (!callback) {
    throw '未提供拦截器方法！';
  }

  if (typeof callback != 'function') {
    throw '提供的拦截器不是函数！';
  }

  interceptors.push(callback);
}

function run (appRoot, routers, port = 3300, enableDbApi = true) {
  if (!appRoot) {
    throw '未设置应用程序根目录！';
  }

  if (!routers) {
    throw '未提供koa路由！';
  }

  if (!Array.isArray(routers)) {
    throw 'koa路由不是数组！';
  }

  if (routers.find(r => !(r instanceof Router))) {
    throw 'koa路由数组中存在不是koa-router类型的对象！';
  }

  app.context.env = env;
  app.context.httpClient = httpClient;
  app.context.getDbHelper = getDbHelper;
  app.context.getResponse = getResponse;

  if (env.sqlite && env.sqlite.file) {
    app.context.sqlite = getSQLite(path.resolve(appRoot, env.sqlite.file));

    global.sqlite = app.context.sqlite;
  }

  app.context.logger = require('./db/Logger').use(app.context.sqlite || app.context.getDbHelper(0));

  // app.use(logger());

  // 跨域处理中间件。
  app.use(cors({
    origin: '*',
    maxAge: 300,
    credentials: true,
    allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
    exposeHeaders: ['Cache-Control', 'Content-Length', 'Content-Type', 'Expires'],
    allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'User-Agent', 'Cache-Control', 'X-Requested-With'],
  }));

  // http请求体处理中间件。
  app.use(koabody({
    multipart: true,
    formLimit: '100mb',
    jsonLimit: '250mb',
    textLimit: '250mb',
    formidable: {
      maxFileSize: 200 * 1024 * 1024,
      hashAlgorithm: 'sha1'
    },
    enableTypes: ['json', 'form', 'text']
  }));

  // http响应json化。
  app.use(json());

  // 注册hbs帮助器。
  hbsHeper.register();

  // 基于hbs的视图引擎。
  app.use(hbs.middleware({
    viewPath: `${appRoot}/app/view`,
    layoutsPath: `${appRoot}/app/view/layout`,
    defaultLayout: 'default'
  }));

  const checkSignature = (q) => {
    var signature = q.signature;
    var echostr = q.echostr;
    var token = env.weChatToken;
    var timestamp = q.timestamp;
    var nonce = q.nonce;

    var array = new Array(token, timestamp, nonce);

    array.sort();

    var str = array.toString().replace(/,/g, "");
    var sha1Code = crypto.createHash("sha1");
    var code = sha1Code.update(str, 'utf-8').digest("hex");

    if (code == signature) {
      return echostr;
    }

    return 'error';
  }

  // 自定义拦截。
  app.use(async (ctx, next) => {
    let start = new Date();

    ctx.rootPath = appRoot;

    // 微信接入验证token处理
    if (ctx.request.query['signature']) {
      let data = checkSignature(ctx.request.query);

      ctx.body = data;

      return;
    }

    // api前缀的请求Content-Type默认为json
    if (ctx.request.path.startsWith('/api/') && ctx.request.path != '/api/health') {
      ctx.response.type = 'json';
    }

    try {
      // 拦截器处理
      if (interceptors && interceptors.length > 0) {
        for (let interceptor of interceptors) {
          let rs = await interceptor(ctx, next);

          if (rs == false) {
            let ms = new Date() - start;

            // 在Http响应头中记录响应时间。
            ctx.set('X-Response-Time', `${ms}ms`);

            return;
          }
        }
      }

      await next();

      let status = ctx.status;
      let ms = new Date() - start;

      // 在Http响应头中记录响应时间。
      ctx.set('X-Response-Time', `${ms}ms`);

      if (status >= 400) {
        ctx.body = getResponse(`出现错误，错误状态：${status}`);

        ctx.logger.error({
          request: {
            url: ctx.url,
            method: ctx.method,
            header: ctx.header,
            query: ctx.query,
            body: ctx.request.body
          },
          message: `出现错误，错误状态：${status}`
        });
      } else {
        ctx.logger.debug(`${ctx.request.path} 调用成功，耗时${ms}ms`);
      }
    } catch (err) {
      let rs = getResponse(err);

      ctx.body = rs;
      ctx.status = 500;

      let ms = new Date() - start;

      // 在Http响应头中记录响应时间。
      ctx.set('X-Response-Time', `${ms}ms`);
      ctx.logger.error({
        request: {
          url: ctx.url,
          method: ctx.method,
          header: ctx.header,
          query: ctx.query,
          body: ctx.request.body
        },
        message: err
      });
    }
  });

  // 注册koa2路由。
  let routes = [];
  let allRouters = [
    require('./controllers/ApiController'),
    require('./controllers/FileController'),
    require('./controllers/LoggerController')
  ];

  if (enableDbApi) {
    allRouters.push(
      require('./controllers/DatabaseController'),
      require('./controllers/DatabaseCompareController'))
  }

  allRouters.push(...routers);

  allRouters.forEach(router => {
    routes.push(...router.stack);

    router.context = app.context;

    app.use(router.routes(), router.allowedMethods());
  });

  global.routes = routes;

  // 静态文件处理中间件
  app.use(static(appRoot + '/assets'));
  app.use(static(appRoot + '/wwwroot'));
  staticPaths.forEach(f => app.use(static(f)));

  // 设置端口号。
  app.port = parseInt(process.env.PORT || port);

  // koa2服务器启动。
  server = app.listen({
    host: '::',
    port: app.port
  }, () => {
    console.log(`http://localhost:${app.port} 已经启动(时间：${new Date().toLocaleString()})...`);
  });

  useConsul();
}

function useConsul () {
  if (!server) {
    throw '当前app应用未启动，使用consul前必须先启动应用！';
  }

  if (!env.consul || !env.consul.enable) {
    return;
  }

  let con = new consul({
    host: env.consul.host,
    port: env.consul.port,
    promisify: true
  });

  con.agent.service.register({
    name: 'monitor',
    address: env.consul.address,
    port: app.port,
    tags: ['http'],
    meta: {
      label: 'monitor',
      description: '解忧杂货铺'
    },
    check: {
      http: `http://${env.consul.address}:${app.port}/api/health`,
      interval: env.consul.interval,
      timeout: env.consul.timeout
    }
  }, (err, result) => {
    if (err) {
      console.log('出现错误,', err);
    } else {
      console.log('Consul注册成功！');
    }
  });
}

module.exports = {
  run,
  useStatic,
  useInterceptor
};